home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-09-14 | 17.9 KB | 437 lines | [TEXT/CCL2] |
- ;;;-*- Mode: Lisp; Package: cl-user -*-
-
- (in-package :cl-user)
-
- ;; applescript.lisp
-
- ;; Author T. Bonura, 1994
- ;; ©Apple Computer
- ;;____________________________________________________________
- ;; Documentation:
- ;; Revisions from the first version
- ;;
- (require :aestuff)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; AppleScript.lisp - T. Bonura 2/3/94
- ;;;
- ;;; Note that this is © Apple Computer, Inc. 1994. All rights reserved.
- ;;; Class definitions for creating applescript CLOS instances.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Changes:
- ;; Fri, Mar 11, 1994 3:02 PM Changed EXECUTE-APPLESCRIPT to check for an open
- ;; component and also a compiled script id.
- ;; Fri, Mar 11, 1994 3:02 PM Changed EXTRACT-THE-RESULT so that it extracts
- ;; the right thing now - which seems to be an id of 1+ the compiled-script-id.
- ;;; Fri, Mar 25, 1994 2:30 PM Added recordability to the functionality of the ASO.
- ;; 9/12/94
- ;; Nathan Wilson - changed the extract result method to be a bit more general.
- ;; It now extracts the result into a descriptor record of (wild type). The
- ;; user is then obliged to dispose of it properly. Now types other than chars
- ;; can be gotten from an application. See the method show-result-as-string to
- ;; see how to coerce from wild type to a string.
- ;; 9/12/94 Put everything in the cl-user package.
- ;; 9/12/94 Think memory leak is fixed - see CLEANUP.
- ;; New:
- ;; According to Inside Mac IAC Chapter 10, an application can maintain several
- ;; connections to a single scripting component or it can have connections to
- ;; several components at the same time. Since AppleScript can only execute a
- ;; single script at a time per component instance, a multithreaded application
- ;; has to provide a seperate component instance for each script that it
- ;; compiles or executes while it is simultaneously executing other scripts. But
- ;; MCL is not multi threaded so, I have defined a global called
- ;; *multiple-component-instances* which is initialized to NIL. If NIL
- ;; there will only be a single scripting component bound to the variable
- ;; *current-scripting-component*. When an instance of an AppleScript
- ;; object is created, if *multiple-component-instances* is NIL (the
- ;; default) then the value of the component slot will be set to the VALUE of
- ;; the global *current-scripting-component*. Otherwise the applescript
- ;; object will get its own instance of a scripting component. There is more
- ;; overhead with creating multiple instances of the scripting component,
- ;; though and they all must be appropriately cleaned up.
-
- ;; When lisp is shut down or when the value of *multiple-component-instances*
- ;; is reset via (use-multiple-component-instances-p) then the
- ;; *current-scripting-component* is disposed.
- ;;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ;; changes:
- ;; • stop-recording - fixed bug where if there was a compiled script id, it would
- ;; immediately return without recording. TB 9/14
- ;; • stop-recording - check for a component before calling open-component TB 9/14
- ;; • applescript-object class def - initialize the slot returned-value to NIL TB
- ;; 9/14
- ;; • cleanup - call dispose method on aedesc reather than the trap. TB 9/14
-
- ;; Bugs:
-
-
- (DEFVAR *multiple-component-instances* NIL "Allows for multiple instances of a
- scripting component. Set this using the function (use-multiple-component-instances-p t) ")
-
- (DEFPARAMETER *current-scripting-component* NIL "A pointer to the current
- scripting component if the application is only using a single component instance"
- )
-
-
- (DEFCONSTANT $AppleScript :|ascr| "The applescript scripting component")
- (DEFCONSTANT $GeneralScriptingComponent :|cscr| "The general scripting component")
- (DEFCONSTANT $HyperTalk :|htlk| "The hypertalk scripting component")
-
- (DEFUN CLOSE-SCRIPTING-COMPONENT ()
- (if (pointerp *current-scripting-component*)
- (#_closeComponent *current-scripting-component*)))
-
- (pushnew #'close-scripting-component *lisp-cleanup-functions*)
-
- (DEFUN USE-MULTIPLE-COMPONENT-INSTANCES-P (boolean)
- (cond (boolean
- (setf *multiple-component-instances* t
- *current-scripting-component* nil)
- (if (pointerp *current-scripting-component*)
- (#_closeComponent *current-scripting-component*)))
- ))
-
- (DEFUN NULL-AEDESC ()
- (let ((result (make-record :aedesc)))
- (rset result :aedesc.descriptorType #$typeNull)
- (rset result :aedesc.dataHandle (%null-ptr))
- result))
-
- (DEFUN AEDESC-P (thing)
- "Returns T if thing if it is an aesedc, otherwise NIL"
- (eq (class-of thing)
- (find-class 'aedesc)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Class: APPLESCRIPT-OBJECT
- ;;; OPEN-COMPONENT ((ASO APPLESCRIPT-OBJECT)) "opens a scripting component
- ;;; and sets the value of the component slot to a pointer
- ;;; COMPILE-APPLESCRIPT ((ASO APPLESCRIPT-OBJECT)) "compiles the script
- ;;; which is in the script slot"
- ;;; EXECUTE-APPLESCRIPT ((ASO APPLESCRIPT-OBJECT)) "What do you think?"
- ;;; EDIT-SCRIPT ((ASO APPLESCRIPT-OBJECT))
- ;;; CLEANUP ((ASO APPLESCRIPT-OBJECT))
- ;;; DISPOSE-SCRIPT ((ASO APPLESCRIPT-OBJECT))
- ;;; DISPLAY-RESULT ((ASO APPLESCRIPT-OBJECT)) - elided - TB 9/12
- ;;; EXTRACT-THE-RESULT ((ASO APPLESCRIPT-OBJECT))
- ;;; ****** Recording *****
- ;;; START-RECORDING ((ASO APPLESCRIPT-OBJECT))
- ;;; STOP-RECORDING ((ASO APPLESCRIPT-OBJECT)) "When we
- ;;; stop recording, we add the decompiled script to the script slot"
- ;;; DECOMPILE-SCRIPT ((ASO APPLESCRIPT-OBJECT))
- ;;; ** ASO = AppleScriptObject **
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (DEFCLASS APPLESCRIPT-OBJECT (standard-object)
- ((script :initform NIL :initarg :script :accessor script)
- (application-name :initform NIL :initarg :application-name :accessor
- application-name)
- (scripting.component.type :initform NIL :initarg :scripting-component-type
- :accessor scripting-component-type)
- ;; NOTE: the as.target slot is not currently used
- (as.target :initform NIL :initarg :target :accessor as-target)
- (break.on.error :initarg :break-on-error :accessor break-on-error)
- (compiled.script :initform NIL :initarg NIL :accessor compiled-script)
- (compiled.script.id :initform NIL :initarg NIL :accessor compiled-script-id)
- (component :initform nil :initarg :component :accessor component)
- (returned.value :initarg :returned-value :accessor returned-value)
- )
- (:default-initargs
- :scripting-component-type $AppleScript
- :break-on-error t
- :returned-value NIL)
- )
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (DEFGENERIC OPEN-COMPONENT (APPLESCRIPT-OBJECT)
- (:documentation "Opens a scripting component. We only open a new scripting
- component if the global *multiple-component-instances* is t. Otherwise all the
- scripts use the same scripting component.")
- )
-
- (DEFMETHOD OPEN-COMPONENT ((ASO APPLESCRIPT-OBJECT))
- ;; changed to deal with either a single or multiple components
- (cond (*multiple-component-instances*
- (setf (component ASO)
- (#_OpenDefaultComponent #$kOSAComponentType
- (scripting-component-type ASO))))
- ((or (null *current-scripting-component*)
- (%null-ptr-p *current-scripting-component*))
- (setf *current-scripting-component*
- (#_OpenDefaultComponent #$kOSAComponentType
- (scripting-component-type ASO)))
- (setf (component ASO) *current-scripting-component*))
- (t (setf (component ASO) *current-scripting-component*))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (DEFGENERIC COMPILE-APPLESCRIPT (APPLESCRIPT-OBJECT)
- (:documentation "Compile the applescript")
- )
-
- (DEFMETHOD COMPILE-APPLESCRIPT ((ASO APPLESCRIPT-OBJECT))
- (unless (component aso)
- (open-component aso))
- (with-pointers ((as (component ASO)))
- (let ((text (script ASO)))
- (with-aedescs (source)
- (let ((size (length text)))
- (%vstack-block (buff size)
- (dotimes (i size)
- (%put-byte buff (char-code (char text i)) i))
- (#_AECreateDesc #$typeChar buff size source)))
- (rlet ((id :OSAID))
- (%put-long id #$kOSANullScript)
- (let ((err (#_OSACompile as source 0 id)))
- (cond ((zerop err)
- ;;(format t "OK Seems to compile")
- (setf (compiled-script aso) t)
- (setf (compiled-script-id ASO) (%get-long id)))
- (t
- (if (break-on-error ASO)
- (error (script-error as)))
- (values nil err))))))
- )
- )
- )
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (DEFGENERIC EXECUTE-APPLESCRIPT (APPLESCRIPT-OBJECT)
- (:documentation "Execute the script on the target")
- )
-
- ;;; Typically returns an AEDesc that the user must dispose. May return a
- ;;; string or nil if bad things happen. The AEDESC is put in the returned-value
- ;;; slot of the object
- (DEFMETHOD EXECUTE-APPLESCRIPT ((ASO APPLESCRIPT-OBJECT))
- ;; in case we try to execute without having an open scripting component
- (unless (component ASO)
- (open-component ASO))
- ;; whenever the script is edited in the script editor, the value of
- ;; compiled-script is set to nil
- (unless (compiled-script aso)
- (compile-applescript ASO))
- (let ((id (compiled-script-id ASO))
- (err nil)
- (result nil)
- (as (component ASO)))
- (rlet ((result-id :OSAID #$typeChar))
- (setq err (#_OSAExecute as id 0 0 result-id))
- (cond
- ((zerop err)
- (unwind-protect
- (setq result (extract-the-result aso (%get-long result-id)))
- (#_OSADispose as (%get-long result-id)))
- result)
- (t
- (if (break-on-error ASO)
- (error (script-error as)))
- (values nil err))))))
-
- (DEFMETHOD EXTRACT-STRING ((aso applescript-object) result-id)
- (with-aedescs (source)
- (let ((err (#_OSADisplay (component aso) result-id #$typeChar 0 source)))
- (cond ((zerop err)
- (setf (returned-value aso)
- (as-get-string (rref source AEDesc.dataHandle))))
- (t (values nil err))))))
-
- ;;; Returns either a string or an AEDesc object. If the latter, the user is responsible for
- ;;; calling dispose on it.
- (DEFMETHOD EXTRACT-THE-RESULT ((aso applescript-object) result-id)
- (let ((as (component aso)))
- (with-aedescs (resultDesc)
- (let ((err (#_OSACoerceToDesc as result-id #$typeWildCard #$kOSAModeNull resultDesc)))
- (cond
- ((zerop err)
- (let ((final-result (null-aedesc)))
- (setq err (#_AEDuplicateDesc resultDesc final-result))
- (cond
- ((zerop err) (setf (returned-value aso)
- (make-instance 'AEDesc :descRecPtr final-result)))
- (t (unless (eql final-result (%null-ptr))
- (#_AEDisposeDesc final-result))
- (extract-string aso result-id)))))
- ;;; If OSACoerceToDesc fails then try to return a string
- (t (extract-string aso result-id)))))))
-
-
- ;; Extract the result creates a descriptor record containing the data (of any
- ;; type). THis data might be a pict or just about anything that the target
- ;; sends back. The following shows how to deal with coercing the descriptor to
- ;; type char so that we can show it as a string.
-
- (DEFMETHOD SHOW-RESULT-AS-STRING ((aso applescript-object))
- (with-aedescs (coerced.desc)
- (let ((err (#_AECoerceDesc (getDescRecPtr (returned-value aso))
- #$typechar coerced.desc)))
- (cond
- ((zerop err)
- (as-get-string (rref coerced.desc AEDesc.dataHandle)))))))
-
- (DEFMETHOD DISPOSE-SCRIPT ((ASO APPLESCRIPT-OBJECT))
- (let ((as (component ASO))
- (id (compiled-script-id ASO)))
- (when (and as id)
- (assert (zerop (#_OSADispose as id))))
- )
- )
-
- (DEFMETHOD CLEANUP ((ASO APPLESCRIPT-OBJECT))
- (dispose-script ASO)
- (let ((returned.value (returned-value aso)))
- (cond (returned.value
- (setf (returned-value aso) nil)
- (if (aedesc-p returned.value)
- (dispose returned.value))))
- (cond (*multiple-component-instances*
- (#_CloseComponent (component aso))
- (setf (component aso) nil)
- )
- )
- )
- )
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (DEFGENERIC EDIT-SCRIPT (APPLESCRIPT-OBJECT)
- (:documentation "Bring up a script editor on the script")
- )
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Methods for dealing with error conditions
- ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;;
- ;;; Recording From Recordable Applications
- ;;; The following allows for recording to be turned on.
- ;;; Actions are recorded to the compiled script in the
- ;;; applescript object.
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (DEFMETHOD START-RECORDING ((aso APPLESCRIPT-OBJECT))
- ;; tell the applescript object to start recording
- ;; tell the object to open a scripting component
- (unless (component aso)
- (open-component aso))
- (with-pointers ((as (component aso)))
- (rlet ((id :OSAID))
- (%put-long id #$KOSANullScript)
- (let ((oserr (#_OSAStartRecording as id)))
- (if (zerop oserr)
- (progn
- (format t "Recording is on.~%")
- (setf (compiled-script-id aso) (%get-long id)))
- (if (break-on-error ASO)
- (error (script-error as))))))))
-
-
- (DEFMETHOD STOP-RECORDING ((aso APPLESCRIPT-OBJECT))
- (with-pointers ((as (component aso)))
- (let ((oserr (#_OSAStopRecording as (compiled-script-id aso))))
-
- (cond ((zerop oserr)
- (decompile-script aso)
- (format t "Recording is off.~%"))
- (t
- (if (break-on-error ASO)
- (error (script-error as))))))))
-
-
- (DEFMETHOD DECOMPILE-SCRIPT ((aso applescript-object))
- ;; extract the script from the compiled script. Most
- ;; useful when doing recording
- (with-pointers ((as (component aso)))
- (let* ((descObj (make-instance 'aedesc :type #$TypeChar))
- (id (compiled-script-id aso))
- (err (#_OSAGetSource as id #$typeChar
- (getDescRecPtr descObj))))
- (cond ((zerop err)
- ;; extract the text from the descriptor, then
- ;; add the script to the script slot of the
- ;; object and inform the object that the
- ;; script has changed
- (setf (script aso)
- (as-get-string (rref (getDescRecPtr descObj)
- AEDesc.dataHandle))
- (compiled-script aso) t))
- (t (if (break-on-error ASO)
- (error (script-error as)))
- )
- )
- )
- )
- )
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Utilities
- ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (DEFUN EXTRACT-SCRIPT-TEXT (text)
- "Remove any tabs and linefeeds from the text if the script should be of short
- form, otherwise if the script is of the type 'tell, end tell' just return the
- whole thing"
- ;; see if the last word of the text is "tell"
- (if (string= "tell" (reverse (string-downcase (subseq (reverse text) 0 4))))
- text
- (strip-lf&tab text)))
-
- (DEFUN STRIP-LF&TAB (string)
- "Removes linefeeds and tabs from a copy of string"
- (substitute-if #\space #'(lambda (char)
- (or (char= char #\return)
- (char= char #\tab)))
- string))
-
-
- (DEFUN AS-GET-STRING (data)
- (let* ((size (#_GetHandleSize data))
- (text (make-string size)))
- (dotimes (i size)
- (setf (char text i) (code-char (%hget-byte data i))))
- text))
-
- (DEFUN SCRIPT-ERROR (as)
- (with-aedescs (err)
- (if (/= (#_OSAScriptError as #$kOSAErrorMessage #$typeChar err) #$noErr)
- ""
- (as-get-string (rref err AEDesc.dataHandle)))))
-
-
- (provide :appleScript)
-
-
- #|
-
- (setf ttest (make-instance 'applescript-object))
- (start-recording ttest)
- (inspect ttest)
- (execute-applescript ttest)
- (cleanup ttest2)
- (decompile-script ttest)
-
-
- (setf astest (make-instance 'APPLESCRIPT-OBJECT
- :script "tell \"Scriptable Text Editor\" to set the size of word 1 of window 1 of application \"Scriptable Text Editor\" to 48"
- ))
- (open-component astest)
- (compile-applescript astest)
- (execute-applescript astest)
- (edit-script astest)
- (cleanup astest)
- Here are some scripts which seem to work:
- "tell application \"Eudora2.0.2a1d-2.1994\" to make new Message at the end of Mailbox \"out\" of Mail Folder \"\""
- "tell application \"Eudora2.0.2a1d-2.1994\" to Connect with send and check"
- "tell application \"Eudora2.0.2a1d-2.1994\" to Reply Message 4 of Mailbox \"In\" of Mail Folder \"\""
- "tell application \"Eudora2.0.2a1d-2.1994\" to Redirect the last Message of Mailbox \"In\" of Mail Folder \"\""
- "tell application \"Eudora2.0.2a1d-2.1994\" to get the Field \"to\" of the last Message of Mailbox \"In\" of Mail Folder \"\""
- "tell application \"Eudora2.0.2a1d-2.1994\" to get the number of Message of Mailbox \"In\" of Mail Folder \"\""
- "tell application \"Eudora2.0.2a1d-2.1994\" to Connect without Send"
-
- ;; here's one for :|quil|
- "tell \"Scriptable Text Editor\" to set the size of word 1 of window 1 of application \"Scriptable Text Editor\" to 48"
-
- |#
-